/*
 * Copyright (c) 2011-2020, baomidou (jobob@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.baomidou.mybatisplus.generator.config;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.config.po.LikeTable;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * 策略配置项
 *
 * @author YangHu, tangguo, hubin
 * @since 2016/8/30
 */
@Data
@Accessors(chain = true)
public class StrategyConfig {

  /**
   * 是否大写命名
   */
  private boolean isCapitalMode = false;
  /**
   * 是否跳过视图
   */
  private boolean skipView = false;
  /**
   * 名称转换
   */
  private INameConvert nameConvert;
  /**
   * 数据库表映射到实体的命名策略
   */
  private NamingStrategy naming = NamingStrategy.no_change;
  /**
   * 数据库表字段映射到实体的命名策略
   * <p>未指定按照 naming 执行</p>
   */
  private NamingStrategy columnNaming = null;
  /**
   * 表前缀
   */
  @Setter(AccessLevel.NONE)
  private final Set<String> tablePrefix = new HashSet<>();
  /**
   * 字段前缀
   */
  @Setter(AccessLevel.NONE)
  private final Set<String> fieldPrefix = new HashSet<>();
  /**
   * 自定义继承的Entity类全称，带包名
   */
  @Setter(AccessLevel.NONE)
  private String superEntityClass;
  /**
   * 自定义基础的Entity类，公共字段
   */
  @Setter(AccessLevel.NONE)
  private final Set<String> superEntityColumns = new HashSet<>();
  /**
   * 自定义继承的Mapper类全称，带包名
   */
  private String superMapperClass = ConstVal.SUPER_MAPPER_CLASS;
  /**
   * 自定义继承的Service类全称，带包名
   */
  private String superServiceClass = ConstVal.SUPER_SERVICE_CLASS;
  /**
   * 自定义继承的ServiceImpl类全称，带包名
   */
  private String superServiceImplClass = ConstVal.SUPER_SERVICE_IMPL_CLASS;
  /**
   * 自定义继承的Controller类全称，带包名
   */
  private String superControllerClass;
  /**
   * 需要包含的表名，允许正则表达式（与exclude二选一配置）<br/> 当{@link #enableSqlFilter}为true时，正则表达式无效.
   */
  @Setter(AccessLevel.NONE)
  private final Set<String> include = new HashSet<>();

  @Setter(AccessLevel.NONE)
  private TableIncludeCallback tableIncludeCallback;
  /**
   * 需要排除的表名，允许正则表达式<br/> 当{@link #enableSqlFilter}为true时，正则表达式无效.
   */
  @Setter(AccessLevel.NONE)
  private final Set<String> exclude = new HashSet<>();
  /**
   * 实体是否生成 serialVersionUID
   */
  private boolean entitySerialVersionUID = true;
  /**
   * 【实体】是否生成字段常量（默认 false）<br> -----------------------------------<br> public static final String ID = "test_id";
   */
  private boolean entityColumnConstant = false;
  /**
   * 【实体】是否为构建者模型（默认 false）<br> -----------------------------------<br> public User setName(String name) { this.name =
   * name; return this; }
   *
   * @deprecated 3.3.2 {@link #chainModel}
   */
  @Deprecated
  private boolean entityBuilderModel = false;

  /**
   * 【实体】是否为链式模型（默认 false）<br> -----------------------------------<br> public User setName(String name) { this.name =
   * name; return this; }
   *
   * @since 3.3.2
   */
  private boolean chainModel = false;

  /**
   * 【实体】是否为lombok模型（默认 false）<br>
   * <a href="https://projectlombok.org/">document</a>
   */
  private boolean entityLombokModel = false;
  /**
   * Boolean类型字段是否移除is前缀（默认 false）<br> 比如 : 数据库字段名称 : 'is_xxx',类型为 : tinyint. 在映射实体的时候则会去掉is,在实体类中映射最终结果为 xxx
   */
  private boolean entityBooleanColumnRemoveIsPrefix = false;
  /**
   * 生成 <code>@RestController</code> 控制器
   * <pre>
   *      <code>@Controller</code> -> <code>@RestController</code>
   * </pre>
   */
  private boolean restControllerStyle = false;
  /**
   * 驼峰转连字符
   * <pre>
   *      <code>@RequestMapping("/managerUserActionHistory")</code> -> <code>@RequestMapping("/manager-user-action-history")</code>
   * </pre>
   */
  private boolean controllerMappingHyphenStyle = false;
  /**
   * 是否生成实体时，生成字段注解
   */
  private boolean entityTableFieldAnnotationEnable = false;
  /**
   * 乐观锁属性名称
   */
  private String versionFieldName;
  /**
   * 逻辑删除属性名称
   */
  private String logicDeleteFieldName;
  /**
   * 表填充字段
   */
  private List<TableFill> tableFillList = null;
  /**
   * 启用sql过滤，语法不能支持使用sql过滤表的话，可以考虑关闭此开关.
   *
   * @since 3.3.1
   */
  private boolean enableSqlFilter = true;
  /**
   * 包含表名
   *
   * @since 3.3.0
   */
  private LikeTable likeTable;
  /**
   * 不包含表名
   *
   * @since 3.3.0
   */
  private LikeTable notLikeTable;

  /**
   * 大写命名、字段符合大写字母数字下划线命名
   *
   * @param word 待判断字符串
   */
  public boolean isCapitalModeNaming(String word) {
    return isCapitalMode && StringUtils.isCapitalMode(word);
  }

  /**
   * 表名称包含指定前缀
   *
   * @param tableName 表名称
   * @deprecated 3.3.2 {@link #startsWithTablePrefix(String)}
   */
  @Deprecated
  public boolean containsTablePrefix(String tableName) {
    return getTablePrefix().stream().anyMatch(tableName::contains);
  }

  /**
   * 表名称匹配表前缀
   *
   * @param tableName 表名称
   * @since 3.3.2
   */
  public boolean startsWithTablePrefix(String tableName) {
    return getTablePrefix().stream().anyMatch(tableName::startsWith);
  }

  public NamingStrategy getColumnNaming() {
    // 未指定以 naming 策略为准
    return Optional.ofNullable(columnNaming).orElse(naming);
  }

  public StrategyConfig setTablePrefix(String... tablePrefix) {
    this.tablePrefix.addAll(Arrays.asList(tablePrefix));
    return this;
  }

  public boolean includeSuperEntityColumns(String fieldName) {
    // 公共字段判断忽略大小写【 部分数据库大小写不敏感 】
    return superEntityColumns.stream().anyMatch(e -> e.equalsIgnoreCase(fieldName));
  }

  public StrategyConfig setSuperEntityColumns(String... superEntityColumns) {
    this.superEntityColumns.addAll(Arrays.asList(superEntityColumns));
    return this;
  }

  public StrategyConfig setInclude(String... include) {
    this.include.addAll(Arrays.asList(include));
    return this;
  }

  public StrategyConfig setTableIncludeCallback(TableIncludeCallback callback) {
    this.tableIncludeCallback = callback;
    return this;
  }

  public StrategyConfig setExclude(String... exclude) {
    this.exclude.addAll(Arrays.asList(exclude));
    return this;
  }

  public StrategyConfig setFieldPrefix(String... fieldPrefixs) {
    this.fieldPrefix.addAll(Arrays.asList(fieldPrefixs));
    return this;
  }

  /**
   * 设置实体父类
   *
   * @param superEntityClass 类全名称
   * @return this
   */
  public StrategyConfig setSuperEntityClass(String superEntityClass) {
    this.superEntityClass = superEntityClass;
    return this;
  }


  /**
   * <p>
   * 设置实体父类，该设置自动识别公共字段<br/> 属性 superEntityColumns 改配置无需再次配置
   * </p>
   * <p>
   * 注意！！字段策略要在设置实体父类之前有效
   * </p>
   *
   * @param clazz 实体父类 Class
   * @return
   */
  public StrategyConfig setSuperEntityClass(Class<?> clazz) {
    this.superEntityClass = clazz.getName();
    return this;
  }

  /**
   * <p>
   * 设置实体父类，该设置自动识别公共字段<br/> 属性 superEntityColumns 改配置无需再次配置
   * </p>
   *
   * @param clazz        实体父类 Class
   * @param columnNaming 字段命名策略
   * @return
   */
  public StrategyConfig setSuperEntityClass(Class<?> clazz, NamingStrategy columnNaming) {
    this.columnNaming = columnNaming;
    this.superEntityClass = clazz.getName();
    return this;
  }

  public StrategyConfig setSuperServiceClass(Class<?> clazz) {
    this.superServiceClass = clazz.getName();
    return this;
  }

  public StrategyConfig setSuperServiceClass(String superServiceClass) {
    this.superServiceClass = superServiceClass;
    return this;
  }

  public StrategyConfig setSuperServiceImplClass(Class<?> clazz) {
    this.superServiceImplClass = clazz.getName();
    return this;
  }

  public StrategyConfig setSuperServiceImplClass(String superServiceImplClass) {
    this.superServiceImplClass = superServiceImplClass;
    return this;
  }

  public StrategyConfig setSuperControllerClass(Class<?> clazz) {
    this.superControllerClass = clazz.getName();
    return this;
  }

  public StrategyConfig setSuperControllerClass(String superControllerClass) {
    this.superControllerClass = superControllerClass;
    return this;
  }

  public Set<String> getInclude() {
    if (!include.isEmpty()) {
      return include;
    }
    return new HashSet<>(Arrays.asList(tableIncludeCallback.getTables()));
  }

  /**
   * <p>
   * 父类 Class 反射属性转换为公共字段
   * </p>
   *
   * @param clazz 实体父类 Class
   */
  protected void convertSuperEntityColumns(Class<?> clazz) {
    List<Field> fields = TableInfoHelper.getAllFields(clazz);
    this.superEntityColumns.addAll(fields.stream().map(field -> {
      TableId tableId = field.getAnnotation(TableId.class);
      if (tableId != null && StringUtils.isNotBlank(tableId.value())) {
        return tableId.value();
      }
      TableField tableField = field.getAnnotation(TableField.class);
      if (tableField != null && StringUtils.isNotBlank(tableField.value())) {
        return tableField.value();
      }
      if (null == columnNaming || columnNaming == NamingStrategy.no_change) {
        return field.getName();
      }
      return StringUtils.camelToUnderline(field.getName());
    }).collect(Collectors.toSet()));
  }

  /**
   * 是否为构建者模型
   *
   * @return 是否为构建者模型
   * @deprecated 3.3.2 {@link #isChainModel()}
   */
  @Deprecated
  public boolean isEntityBuilderModel() {
    return isChainModel();
  }

  /**
   * 设置是否为构建者模型
   *
   * @param entityBuilderModel 是否为构建者模型
   * @return this
   * @deprecated 3.3.2 {@link #setChainModel(boolean)}
   */
  @Deprecated
  public StrategyConfig setEntityBuilderModel(boolean entityBuilderModel) {
    return setChainModel(entityBuilderModel);
  }

  public Set<String> getSuperEntityColumns() {
    if (StringUtils.isNotBlank(this.superEntityClass)) {
      try {
        Class<?> superEntity = ClassUtils.toClassConfident(this.superEntityClass);
        convertSuperEntityColumns(superEntity);
      } catch (Exception e) {
        //当父类实体存在类加载器的时候,识别父类实体字段，不存在的情况就只有通过指定superEntityColumns属性了。
      }
    }
    return this.superEntityColumns;
  }
}
